#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "getputch.h"

#define NO 15      //训练次数

#define KTYPE 16    //位置训练块数

#define POS_LEN 10  //位置训练的字符串长度

#define NO_TIMES_LEFT -1    //不显示剩余训练次数

#define PERMUTE_LEN 3   //排序组合训练长度

//练习菜单
typedef enum {Term, KeyPos, KeyPosComp, Clang, Conversation, KeyboardAssociate, Permutation, InValid} Menu;

//块数据
char *kstr[] = {
    "12345",    "67890-=",
    "!@#$%",    "^&*()_+",
    "qwert",    "yuiop[]\\",
    "QWERT",    "YUIOP{}|",
    "asdfg",    "hjkl;'",
    "ASDFG",    "HJKL:\"",
    "zxcvb",    "nm,./",
    "ZXCVB",    "NM<>?",
};


//C关键字和库函数

char *cstr[] = {
    "auto",     "break",    "case",     "char",     "const",    "continue",
    "default",  "do",       "double",   "else",     "enum",     "extern",
    "float",    "for",      "goto",     "if",       "int",      "long",
    "register", "return",   "short",    "signed",   "sizeof",   "static",
    "struct",   "switch",   "typedef",  "union",    "unsigned", "void",
    "volatile", "while",

    "abort",    "abs",      "acos",     "asctime",  "asin",     "assert",
    "atan",     "atan2",    "atexit",   "atof",     "atoi",     "atol",
    "bsearch",  "calloc",   "ceil",     "clearerr", "clock",    "cos",
    "cosh",     "ctime",    "difftime", "div",      "exit",     "exp",
    "fabs",     "fclose",   "feof",     "ferror",   "fflush",   "fgetc",
    "fgetpos",  "fgets",    "floor",    "fmod",     "fopen",    "fprintf",
    "fputc",    "fputs",    "fread",    "free",     "freopen",  "frexp",
    "fscanf",   "fseek",    "fsetpos",  "ftell",    "fwrite",   "getc",
    "getchar",  "getenv",   "gets",     "gmtime",   "isalnum",  "isalpha",
    "iscntrl",  "isdigit",  "isgraph",  "islower",  "isprint",  "ispunct",
    "isspace",  "isupper",  "isxdigit", "labs",     "ldexp",    "ldiv",
    "localeconv",           "localtime","log",      "log10",    "longjmp",
    "malloc",   "memchr",   "memcmp",   "memcpy",   "memmove",  "memset",
    "mktime",   "modf",     "perror",   "pow",      "printf",   "putc",
    "putchar",  "puts",     "qsort",    "raise",    "rand",     "realloc",
    "remove",   "rename",   "rewind",   "scanf",    "setbuf",   "setjmp",
    "setlocale","setvbuf",  "signal",   "sin",      "sinh",     "sprintf",
    "sqrt",     "srand",    "sscanf",   "strcat",   "strchr",   "strcmp",
    "strcoll",  "strcpy",   "strcspn",  "strerror", "strftime", "strlen",
    "strncat",  "strncmp",  "strncpy",  "strpbrk",  "strrchr",  "strspn",
    "strstr",   "strtod",   "strtok",   "strtol",   "strtoul",  "strxfrm",
    "system",   "tan",      "tanh",     "time",     "tmpfile",  "tmpnam",
    "tolower",  "toupper",  "ungetc",   "va_arg",   "va_end",   "va_start",
    "vfprintf", "vprintf",  "vsprintf",
};

char *vstr[] = {
    "Hello!",
    "How are you?",
    "Fine thanks.",
    "I can't complain, thanks.",
    "How do you do?",
    "Good bye!",
    "Good morning!",
    "Good afternoon!",
    "Good evening!",
    "see you later!",
    "Go ahead, please.",
    "Thank you.",
    "No, thank you.",
    "May I have your name?",
    "I'm glad to meet you.",
    "What time is it now?",
    "It's about seven.",
    "How much?",
    "Where is the restroom?",
    "Excuse me.",
    "Excuse us.",
    "I'm sorry.",
    "I don't know.",
    "I have no change with me.",
    "I will be back.",
    "Are you going out?",
    "I hope I'm not disturbing you.",
    "I'll offer no excuse.",
    "Shall we dance?",
    "Will you do me a favor?",
    "It's very unseasonable.",
    "You are always welcome.",
    "Hold still!",
    "Follow me.",
    "Just follow my lead.",
    "To be honest with you,",
};

/**
 * 打字训练核心
 * @param const char* s 训练字符串
 * @param int times 如果>=0,则显示剩余训练次数.
 */
int go(const char* s, int times)
{
    int mistake = 0;
    int i =0;
    int j =0;
    int len = strlen(s);

    for (; i<len; ++i,j=i) {
        if (times <= NO_TIMES_LEFT) {
            printf("%s \r", &s[i]);

        } else {
            printf("\r[%2d] left> %s \b", times, &s[i]);

            while (j < len) {
                putchar('\b');
                ++j;
            }

        }

        fflush(stdout);

        if (getch() != s[i]) {
            ++mistake;
            putchar('\a');
        }
    }

    if (0 == times) {
        printf("\r----------- end -----------");
    }

    return mistake;
}

void pos_training(){
    int stage;
    int line, temp;
    int len;
    int cno, eno;
    time_t s, e;

    printf("\n进行单一位置训练.\n");
    printf("请选择要练习的块.\n");
    fflush(stdout);

    int i=1;
    for (;i<=4; ++i) {
        printf("第%d层  (%d) 左%-8s (%d) 右%-8s\n", i, i*2-1, kstr[(i-1)*4], i*2, kstr[(i-1)*4+1]);
    }
    fflush(stdout);

    int choice;
    do {
        printf("快编号(停止练习为99):\n");
        fflush(stdout);

        scanf("%d", &choice);
        if (99 == choice) return;
    } while (choice<1 || choice >8);

    //首先 转到4*n 然后右边偶变基余1
    line = 4 * ((choice-1)/2) + (choice - 1) % 2;

    printf("练习题目[%s] %d次!\n", kstr[line], NO);

    printf("按下空格键开始!\n");
    fflush(stdout);

    while (getch() != ' ')
        ;

    cno = eno = 0;
    len = strlen(kstr[line]);

    s = time(NULL);

    stage = 0;
    for (; stage < NO; ++stage) {
        char str[POS_LEN + 1];

        for (int i=0; i<POS_LEN; ++i)
            str[i] = kstr[line][rand() % len];

       str[i] = 0;

       eno += go(str, NO - 1 - stage);
       cno += strlen(str);
    }

    e = time(NULL);

    printf("\n");
    printf("题目共%d字符,错误:%d次\n", cno, eno);
    printf("用时%.1fs\n", (double)e - s);
    fflush(stdout);
}

void pos_training2(){
    int i;

    int stage;
    int tmp;

    int selen;

    int select[KTYPE];
    int len[KTYPE];

    int cno, eno;

    time_t s,e;

    char *format = "第%d层  (%2d) 左%-8s     (%2d) 右%-8s"
                            "(%2d) 左%-8s    (%2d) 右%-8s\n";

    printf("\n进行混合位置训练.\n");
    printf("请选择要练习的块(可多选).\n");
    fflush(stdout);

    for (i=0; i<4; ++i) {
        int k = 4 * i;
        printf(format, i+1, k+1, kstr[k], k+2, kstr[k+1], k+3, kstr[k+2], k+4, kstr[k+3]);
    }
    fflush(stdout);

    selen = 0;

    while (1) {
        printf("\n块编号(选择完毕为50/结束练习为99): ");
        fflush(stdout);

        do {
            scanf("%d", &tmp);

            if (tmp == 99) return;

        } while ((tmp < 1 || tmp > KTYPE) && tmp != 50);

        if (tmp == 50) break;

        for (i=0; i<selen; i++) {
            if (select[i] == tmp) {
                printf("\a这一块已经选过了.\n");
                break;
            }
        }

        fflush(stdout);

        if (i == selen)
            select[selen++] = tmp;

    }

    if (selen == 0) return;

    putchar('\n');

    printf("把下列块的题目练习%d次.\n", NO);

    for (i=0; i<selen; ++i) {
        printf("%s%c", kstr[select[i] - 1], (i>0 && i%3 == 0) ? '\n': ' ');
    }

    putch('\n');
    printf("\n按下空格键开始.\n");
    fflush(stdout);

    while (getch() != ' ')
        ;

    cno = eno = 0;

    s = time(NULL);

    for (stage=0; stage <NO; ++stage) {
        char str[POS_LEN + 1];

        int line = select[rand() % selen] - 1;

        int len = strlen(kstr[line]);

        for (i=0; i<POS_LEN; ++i) {
            str[i] = kstr[line][rand() % len];
        }

        str[i] = 0;
        eno += go(str, NO - 1 - stage);
        cno += strlen(str);
    }

    e = time(NULL);

    printf("\n");
    printf("题目:%d字符数,错误:%d次\n", cno, eno);

    printf("共用去%.1fs\n", (double)e - s);
    fflush(stdout);
}


/**
 * 单词/文档训练
 * 第次题目不重复
 * @param char*   t 题目说明字符串
 * @param char*[] a 题目列表
 * @param int     n 题目数量
 */
void word_training(char *t, char *a[], int n)
{
    int i;
    time_t s, e;

    int stage;

    int cno, eno;

    int ind;

    int books[n];

    int times = NO < n ? NO : n;

    printf("\n练习%d个%s.\n", times, t);
    fflush(stdout);

    cno = eno = 0;

    s = time(NULL);
    for (stage=0; stage<times; ++stage) {
        int ind;
        do {
            ind = rand() % n;
            if (books[ind]) {
                continue;
            }

            books[ind] = 1;
        } while (0);

        eno += go(a[ind], times - 1 - stage);
        cno += strlen(a[ind]);
    }

    e = time(NULL);

    printf("\n");
    printf("题目: %d字符, 错误:%d个\n", cno, eno);
    printf("用时%.1fs\n", (double)e - s);
    fflush(stdout);
}


void kboard_training(){
    int i;
    int stage;
    int s, e;

    int eno;

    eno = 0;

    printf("输入题目中[?]的正确值!\n");
    printf("按空格键开始!\n");
    fflush(stdout);

    while (getch() != ' ')
        ;

    s = time(NULL);

    for (stage=0; stage < NO; ++stage) {
        char tmp[POS_LEN + 1];

        int b = rand() % 16;
        size_t len = strlen(kstr[b]);

        int q_pos = rand() % len;

        for (i=0; i<len; ++i) {
            tmp[i] = (i == q_pos) ? '?' : kstr[b][i];
        }
        tmp[i] = 0;

        printf("\r[%2d] left: %s", NO - 1 - stage, tmp);
        for (i=0; i<len - q_pos; ++i) {
            putchar('\b');
        }
        fflush(stdout);

        if (getch() != kstr[b][q_pos]) {
            putch('\a');
            ++eno;
        }

        printf("\r[%2d] left: %*s", NO - 1 -stage, len, "");
    }

    fflush(stdout);

    e = time(NULL);

    printf("\n");

    printf("题目: %d个, 错误:%d个\n", NO, eno);

    printf("用时%.1fs\n", (double)e - s);

    fflush(stdout);
}


/*
 * 排序训练使用dfs算法递归每一种可能
 *
 * 每一种可能的每一个位置字符都是唯一的
 * 这里只考虑res二个长度的情况
 *
 * @param int[]         res   排列字符串每一步对应的tmp的索引值(元素取值0~len-1)
 * @param int           len   tmp长度,(避免每次计算)取值
 * @param const char*   tmp   数据源字符串
 * @param int           step  当前是第几步
 * @param int[]         book  用来标识哪些位置已经使用
 */
void permute_training_dfs(int *res, int len, const char* tmp, int step, int *book) {
    if (step == PERMUTE_LEN) {
        char temp[3];
        for (int i=0; i<PERMUTE_LEN; ++i) {
            temp[i] = tmp[res[i]];
        }
        printf("%s ", temp);
        //go(temp, NO_TIMES_LEFT);
        return;
    }

    int i;
    for (i=0; i<len; ++i) {
        if (!book[i]) {
            //使用该位置
            res[step] = i;

            //声明占用该位置
            book[i] = 1;
            permute_training_dfs(res, len, tmp, step+1, book);

            //解除对位置的占用
            book[i] = 0;
        }
    }

    return;
}




//排列重复打印
void permute_training(){
    int i=0;
    int stage;

    char* tmp;

    int b;

    int cno, eno;

    time_t s,e;

    printf("选择块,进行任意三个字符的组合训练.i\n");

    printf("请选择块:\n");

    for (i=0; i<4; ++i) {
        int k = 4*i;
        printf("(%d) %-8s\n", i+1, kstr[k]);
    }

    fflush(stdout);

    do {
        printf("\n块编号:");
        fflush(stdout);
        scanf("%d", &b);
    } while (b<1 || b >4);


    tmp = kstr[(b-1)*4];

    printf("使用块:%s\n", kstr[(b-1)*4]);

    fflush(stdout);

    int len = strlen(tmp);

    int res[PERMUTE_LEN] = {0};

    int *book = calloc(len, sizeof(int));

    permute_training_dfs(res, len, tmp, 0, book);

}

int menu()
{
    printf("请选择练习项目:\n");
    fflush(stdout);

    int sel;

    do {
        printf("(1) 单一位置     (2) 混合位置\n");
        printf("(3) C语言的单词  (4) 英语会话\n");
        printf("(5) 键盘联想打字 (6) 排列重复训练\n");
        printf("(0) 结束\n");
        fflush(stdout);

        sel = getch() - '0';
        printf("%d\b", sel);
        fflush(stdout);

    } while (sel < Term || sel >= InValid);

    puts("\n");
    return (Menu)sel;
}


int main(void){
    srand(time(NULL));
    init_getputch();

    size_t cn, vn;

    cn = sizeof(cstr) / sizeof(cstr[0]);

    vn = sizeof(vstr) / sizeof(vstr[0]);

    Menu m;

    do {
        switch (m = menu()) {

            case KeyPos:
                pos_training();
                break;

            case KeyPosComp:
                pos_training2();
                break;

            case Clang:
                word_training("C语言的单词", cstr, cn);
                break;

            case Conversation:
                word_training("英语会话文档", vstr, vn);
                break;

            case KeyboardAssociate:
                kboard_training();
                break;

            case Permutation:
                permute_training();
                break;
        }

        putch('\n');
        putch('\n');

    } while (m != Term);


    term_getputch();

    return 0;
}



